#include "camera.h"
#include "library.h"
#include "shaderloader.h"

void DisplayFunc(void);
void KeyboardFunc(unsigned char key, int x, int y);
void KeyboardUpFunc(unsigned char key, int x, int y);
void SpecialFunc(int key, int x, int y);
void MouseFunc(int button, int state, int x, int y);
void MotionFunc(int x, int y);
void PassiveMotionFunc(int x, int y);
void RenderSkybox();
void RenderSun();

static int window;
static int windowHeight = 600;
static int windowWidth = 800;
static int lastFrameTimestamp = 0;
static bool keys[256];
static Camera *camera;
static Texture skyboxTexture;
static GLuint skyboxShader;
static Texture sunTexture;
static Texture sunmaskTexture;
static float sunmaskFrame = 0.0f;

int main(int argc, char **argv) {
	int i;
	
	// Initialize glut.
	glutInit(&argc, argv);
	
	// Set the display mode.
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	
	// Set the size of the window.
	glutInitWindowSize(windowWidth, windowHeight);
	
	// The position of the window.
	glutInitWindowPosition(100, 100);
	
	// Create the window.
	window = glutCreateWindow("Infiniverse");
	
	// Set the callback functions.
	glutDisplayFunc(DisplayFunc);
	glutKeyboardFunc(KeyboardFunc);
	glutKeyboardUpFunc(KeyboardUpFunc);
	glutSpecialFunc(SpecialFunc);
	glutMouseFunc(MouseFunc);
	glutMotionFunc(MotionFunc);	
	glutPassiveMotionFunc(PassiveMotionFunc);
	//glutIdleFunc(DisplayFunc);
	
	// Initialize OpenGL.
	glViewport(0, 0, windowWidth, windowHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(
		45.0f,
		(float)windowWidth / (float)windowHeight,
		0.5f,
		500.0f
	);
	glMatrixMode(GL_MODELVIEW);
	
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glEnable(GL_BLEND);
	glEnable(GL_TEXTURE_2D);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	// Initialize the key list.
	for(i = 0; i < 256; i++)
		keys[i] = false;
	
	// Initialize the camera.
	Vector cameraPosition = new GLfloat[3];
	cameraPosition[0] = 0;
	cameraPosition[1] = 0;
	cameraPosition[2] = -500.0;
	camera = new Camera(
		cameraPosition,
		0,
		0,
		0
	);
	
	// Initialize GLEW.
	glewInit();
	if(glewIsSupported("GL_VERSION_2_0"))
		printf("Ready for OpenGL 2.0\n");
	else {
		printf("OpenGL 2.0 not supported\n");
		exit(1);
	}
	
	skyboxShader = createShaderProgram("data/shaders/skybox.vert", "data/shaders/skybox.frag");
	skyboxTexture = LoadPNGTexture("data/images/skybox", GL_RGBA, 1);
	sunTexture = LoadPNGTexture("data/images/sun", GL_RGBA, 1);
	sunmaskTexture = LoadPNGTexture("data/images/sunmask", GL_RGBA, 1);
	
	// Enter the main loop.
	glutMainLoop();

	return 0;
}

void DisplayFunc(void) {
	int frameTimestamp;
	int timeElapsed = 0;
	
	frameTimestamp = GetMilliseconds();
	if(lastFrameTimestamp > 0)
		timeElapsed = frameTimestamp - lastFrameTimestamp;
	lastFrameTimestamp = frameTimestamp;
	
	// Clear the screen.
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glLoadIdentity();
	glDisable(GL_DEPTH_TEST);
	glRotatef(camera->GetPitch(), -1.0f, 0.0f, 0.0f);
	glRotatef(camera->GetYaw(), 0.0f, -1.0f, 0.0f);
	glRotatef(camera->GetRoll(), 1.0f, 0.0f, 0.0f);
	
	RenderSkybox();
	RenderSun();
	
	glLoadIdentity();
	glEnable(GL_DEPTH_TEST);
	glRotatef(camera->GetPitch(), -1.0f, 0.0f, 0.0f);
	glRotatef(camera->GetYaw(), 0.0f, -1.0f, 0.0f);
	glRotatef(camera->GetRoll(), 1.0f, 0.0f, 0.0f);
	glTranslatef(
		camera->GetPositionX(),
		camera->GetPositionY(),
		camera->GetPositionZ()
	);
	
	glutSwapBuffers();
	
	camera->Update(timeElapsed);
	
	glutPostRedisplay();
}

void KeyboardFunc(unsigned char key, int x, int y) {
	keys[key] = true;
	switch(key) {
		case 'a':
			if(keys['d']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					0.01,
					camera->GetPitch(),
					camera->GetYaw() + 90,
					camera->GetRoll()
				);
			}
			break;
		case 'd':
			if(keys['a']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					0.01,
					camera->GetPitch(),
					camera->GetYaw() - 90,
					camera->GetRoll()
				);
			}
			break;
		case 'e':
			if(keys['q']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					0.01,
					camera->GetPitch() - 90,
					camera->GetYaw(),
					camera->GetRoll()
				);
			}
			break;
		case 'q':
			if(keys['e']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					0.01,
					camera->GetPitch() + 90,
					camera->GetYaw(),
					camera->GetRoll()
				);
			}
			break;
		case 's':
			if(keys['w']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					-0.01,
					camera->GetPitch(),
					camera->GetYaw(),
					camera->GetRoll()
				);
			}
			break;
		case 'w':
			if(keys['s']) {
				camera->SetVelocityX(0.0);
				camera->SetVelocityY(0.0);
				camera->SetVelocityZ(0.0);
			}
			else {
				camera->Move(
					0.01,
					camera->GetPitch(),
					camera->GetYaw(),
					camera->GetRoll()
				);
			}
			break;
		case 27:
			exit(0);
			break;
	}
}

void KeyboardUpFunc(unsigned char key, int x, int y) {
	keys[key] = false;
}

void SpecialFunc(int key, int x, int y) {
	
}

void MouseFunc(int button, int state, int x, int y) {
	
}

void MotionFunc(int x, int y) {
	
}

void PassiveMotionFunc(int x, int y) {
	float pitchVelocityPercentage;
	pitchVelocityPercentage = ((float)windowHeight / 2.0f - (float)y) / (float)windowHeight / 2.0f;
	camera->SetPitchVelocity(pitchVelocityPercentage);
	float yawVelocityPercentage;
	yawVelocityPercentage = ((float)windowWidth / 2.0f - (float)x) / (float)windowWidth / 2.0f;
	camera->SetYawVelocity(yawVelocityPercentage);
}

void RenderSkybox() {
	glUseProgram(skyboxShader);
	GLint timeLoc = glGetUniformLocation(skyboxShader, "time");
	glUniform1i(timeLoc, GetMilliseconds());
	
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glBindTexture(GL_TEXTURE_2D, skyboxTexture.gluiTexture);
	glBegin(GL_QUADS);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-10.0f, -10.0f, -10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(10.0f, -10.0f, -10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(10.0f, 10.0f, -10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(-10.0f, 10.0f, -10.0f);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-10.0f, -10.0f, 10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(-10.0f, 10.0f, 10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(-10.0f, 10.0f, -10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(-10.0f, -10.0f, -10.0f);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(10.0f, -10.0f, 10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(10.0f, 10.0f, 10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(-10.0f, 10.0f, 10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(-10.0f, -10.0f, 10.0f);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(10.0f, -10.0f, -10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(10.0f, 10.0f, -10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(10.0f, 10.0f, 10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(10.0f, -10.0f, 10.0f);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-10.0f, -10.0f, 10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(10.0f, -10.0f, 10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(10.0f, -10.0f, -10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(-10.0f, -10.0f, -10.0f);
	
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-10.0f, 10.0f, -10.0f);
	glTexCoord2f(4.0f, 0.0f);
	glVertex3f(10.0f, 10.0f, -10.0f);
	glTexCoord2f(4.0f, 4.0f);
	glVertex3f(10.0f, 10.0f, 10.0f);
	glTexCoord2f(0.0f, 4.0f);
	glVertex3f(-10.0f, 10.0f, 10.0f);
	
	glEnd();
	glUseProgram(0);

	glPopMatrix();
	glDisable(GL_TEXTURE_2D);
}

void RenderSun() {
	GLfloat distance;
	GLfloat scale;
	distance = GetDistance(
		camera->GetPosition()
	);
	if(distance > 0) scale = 500.0f / distance;
	else scale = 0.0f;
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glBindTexture(GL_TEXTURE_2D, sunTexture.gluiTexture);
	glRotatef(sunmaskFrame / 2, 0.0f, 0.0f, -1.0f);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-scale, -scale, -15.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(scale, -scale, -15.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(scale, scale, -15.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-scale, scale, -15.0f);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, sunmaskTexture.gluiTexture);
	glRotatef(sunmaskFrame * 2, 0.0f, 0.0f, 1.0f);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-scale, -scale, -15.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(scale, -scale, -15.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(scale, scale, -15.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-scale, scale, -15.0f);
	glEnd();
	glPopMatrix();
	glDisable(GL_TEXTURE_2D);
	sunmaskFrame += 0.01f;
}
